package gpl.pierrick.brihaye.aramorph;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Loads a dictionary from an XML file.
 * @author Ahmed Saad
 */
public class XMLDictionaryLoader extends DefaultHandler {
	private static final Logger logger = Logger.getLogger(XMLDictionaryLoader.class.getName());
	
	private static final Map<String,ElementName> elementsNames;
	/**Tells whether this XML file is hierarchical or list of entries.
	 * It is known from the first tag after the root tag.
	 * If it is &lt;entry&gt; tag, the XML file is not hierarchical.
	 * If it is &lt;root&gt; tag, the XML file is hierarchical.*/
	private boolean isHierarchicalXML;
	/**Is the first element after the root has been reached or not.
	 * Used to correctly determine the type of XML file (hierarchical or flat)*/
	private boolean isFirstElement = true;
	/**The dictionary to be loaded*/
	private InMemoryDictionary dictionary;
	/**The unvocalized of the dictionary entry*/
	private String unvocalized;
	/**The vocalized of the dictionary entry*/
	private String vocalized;
	/**The morphologicalCategory of the dictionary entry*/
	private String morphologicalCateogory;
	/**The glosses of the dictionary entry*/
	private Collection<String> glosses;
	/**The grammaticalCategories of the dictionary entry*/
	private Collection<String> grammaticalCategories;
	/**The lemmaID of the dictionary entry*/
	private String lemmaID;
	/**The root of the dictionary entry*/
	private String root;
	
	/**The pcdata (characters) found in the current element. Will be written when element ends*/
	private String pcdata;
	
	/**Some variables to make statistical calculations*/
	private int entryLen;
	private int lemmaIDLen;
	private int vocalizationLen;
	private int morphologyLen;
	private int glossesLen;
	private int POSLen;
	private int rootLen;

	/**
	 * Creates a new DictionaryLoader that will load the passed dictionary.
	 * @param d the dictionary to be loaded. This must have been constructed and may not be null.
	 */
	public XMLDictionaryLoader(InMemoryDictionary d) {
		this.dictionary = d;
	}
	
	/**
	 * Write down statistics collected during loading
	 */
	public void writeStats() {
		logger.info("Stats for dictionary:");
		logger.info("--------------------");
		logger.info("Max length for entry:"+entryLen);
		logger.info("Max length for lemma Id:"+lemmaIDLen);
		logger.info("Max length for vocalization:"+vocalizationLen);
		logger.info("Max length for morphology:"+morphologyLen);
		logger.info("Max length for glosses:"+glossesLen);
		logger.info("Max length for POS:"+POSLen);
		logger.info("Max length for root:"+rootLen);
	}

	static  {
		//intitialize the mapping from each XML element name to <code>ElementName</code>
		elementsNames = new HashMap<String, ElementName>(59);
		elementsNames.put("entry",ElementName.ENTRY);
		elementsNames.put("unvocalized",ElementName.UNVOCALIZED);
		elementsNames.put("vocalized",ElementName.VOCALIZED);
		elementsNames.put("morphological-category",ElementName.MORPHOLOGICAL_CATEGORY);
		elementsNames.put("gloss",ElementName.GLOSS);
		elementsNames.put("grammatical-category",ElementName.GRAMMATICAL_CATEGORY);
		elementsNames.put("lemmaID",ElementName.LEMMA_ID);
		elementsNames.put("root",ElementName.ROOT);
		elementsNames.put("grammatical-categories",ElementName.GRAMMATICAL_CATEGORIES);
		elementsNames.put("glosses",ElementName.GLOSSES);
		elementsNames.put("lemma",ElementName.LEMMA);
	}
	
	public void characters(char[] ch, int start, int length) throws SAXException {
		pcdata += new String(ch,start,length);
	}

	public void endElement(String uri, String localName, String qName) throws SAXException {
		ElementName element = elementsNames.get(qName);
		if (element == null)
			return;
		switch (element) {
		case ENTRY:
			String[] arGlosses = new String[glosses.size()];
			String[] arGrammaticalCategories = new String[grammaticalCategories.size()];
			DictionaryEntry de = new DictionaryEntry(unvocalized, lemmaID,
					vocalized, morphologicalCateogory, glosses.toArray(arGlosses),
					grammaticalCategories.toArray(arGrammaticalCategories),root);
			
			//signal a complete entry loaded
			entryLoaded(de);
			
			//Make some statistics
			entryLen = Math.max(entryLen,de.getEntry(false).length());
			rootLen = Math.max(rootLen,de.getRoot().length());
			lemmaIDLen = Math.max(lemmaIDLen,de.getLemmaID().length());
			vocalizationLen = Math.max(vocalizationLen,de.getVocalization().length());
			morphologyLen = Math.max(morphologyLen,de.getMorphology().length());
			//calculate total length of glosses with a separating comma
			int len=0;
			for (String temp:de.getGlosses())
				len += temp.length()+1;
			len--;
			glossesLen = Math.max(glossesLen,len);
			//calculate total length of POS with a separating comma
			len=0;
			for (String temp:de.getPOS())
				len += temp.length()+1;
			len--;
			POSLen = Math.max(POSLen,len);
			break;
		case UNVOCALIZED: this.unvocalized = pcdata; break;
		case VOCALIZED: this.vocalized = pcdata; break;
		case MORPHOLOGICAL_CATEGORY: this.morphologicalCateogory = pcdata; break;
		case GLOSS: this.glosses.add(pcdata); break;
		case GRAMMATICAL_CATEGORY: this.grammaticalCategories.add(pcdata);break;
		case ROOT: this.root = "";
		case LEMMA_ID: this.lemmaID = ""; break;
		}
	}
	
	/**
	 * Tells that an entry has been loaded
	 * @param de
	 */
	protected void entryLoaded(DictionaryEntry de) {
		dictionary.addDictionaryEntry(de);
	}

	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
		ElementName element = elementsNames.get(qName);
		if (element == null)
			return;

		if (isFirstElement) {
			isHierarchicalXML = element == ElementName.ROOT;
			isFirstElement = false;
		}

		switch (element) {
		case ENTRY:
			//We need to reset these two because they may not be found and have to have the empty string value
			if (!isHierarchicalXML) {
				lemmaID = "";
				root = "";
			}
			break;
		case GRAMMATICAL_CATEGORIES: grammaticalCategories = new ArrayList<String>();break;
		case GLOSSES: glosses = new ArrayList<String>(); break;
		case LEMMA:
			//a lemma ID that is not inside <entry> tag.
			//we get the lemma from the attribute rather than the text #PCDATA
			if (isHierarchicalXML)
				lemmaID = attributes.getValue(0);
			break;
		case ROOT:
			//a root that is not inside <entry> tage.
			//we get the root from the attribute rather than the text #PCDATA
			if (isHierarchicalXML)
				root = attributes.getValue(0);
			break;
		}
		pcdata = "";
	}
}

enum ElementName {
	ENTRY, UNVOCALIZED, VOCALIZED, MORPHOLOGICAL_CATEGORY, GLOSS, GRAMMATICAL_CATEGORY, LEMMA_ID, ROOT, GRAMMATICAL_CATEGORIES, GLOSSES, LEMMA
}